# 先请求验证码
import datetime
import pickle
import sys
import threading
import time
from ctypes import *

import facade
import requests
from xjlibrary.mdatetime.mtime import getTodayDate, MDateTime
from xjlibrary.mprocesspoll.MThreadingRun import MThreadingRun
from xjlibrary.our_file_dir import BaseDir

curPath = BaseDir.get_file_dir_absolute(__file__)
sys.path.append(curPath)

from base_sipo_shichuan import BaseSipo

curPath = BaseDir.get_file_dir_absolute(__file__)
configfile = BaseDir.get_new_path(curPath, "db.ini")
jpgdir = BaseDir.get_new_path(curPath, "jpg")
BaseDir.create_dir(jpgdir)


class DiscernCode(object):
    """
    将识别验证码封装成一个类
    """

    def __init__(self, logger):
        self.appId = 6249  # 软件ＩＤ，开发者分成必要参数。登录开发者后台【我的软件】获得！
        self.appKey = b'439aa4305465df04545dad067bb31751'  # 软件密钥，开发者分成必要参数。登录开发者后台【我的软件】获得！
        self.YDMApi = windll.LoadLibrary('yundamaAPI-x64')
        # 注意这里是普通会员账号，不是开发者账号，注册地址 http://www.yundama.com/index/reg/user
        # 开发者可以联系客服领取免费调试题分
        # self.username = b'xujiang'
        # self.password = b'xujiang1994323'
        self.username = b'office'
        self.password = b'officeHelper$123'
        # 例：1004表示4位字母数字，不同类型收费不同。请准确填写，否则影响识别率。在此查询所有类型 http://www.yundama.com/price.html
        self.codetype = 1005
        # 分配30个字节存放识别结果
        self.result = c_char_p(b"                              ")
        # 验证码文件路径
        # self.filename = jpgpath.encode('utf-8')
        self.logger = logger
        # 识别超时时间 单位：秒
        self.timeout = 60

    def easy_decode_by_path(self, jpgpath):
        self.logger.info("正在开始一键识别{}".format(jpgpath))
        # 一键识别函数，无需调用 YDM_SetAppInfo 和 YDM_Login，适合脚本调用
        captchaId = self.YDMApi.YDM_EasyDecodeByPath(self.username,
                                                     self.password,
                                                     self.appId,
                                                     self.appKey,
                                                     jpgpath,
                                                     self.codetype,
                                                     self.timeout,
                                                     self.result)

        self.logger.info("一键识别：验证码ID：%d，识别结果：%s" % (captchaId, self.result.value))
        if int(captchaId) > 0:
            return True, str(self.result.value)
        else:
            # 有可能识别正确但返回识别失败 登录试试
            self.logger.error("识别失败 请检查原因")
            return True, str(self.result.value)


class sipogovLogin(BaseSipo):

    def __init__(self):
        self.filelogger = facade.get_timerotatingfilelogger(
            BaseDir.get_new_path(curPath, "logs", "step1_login" + str(threading.get_ident())))
        super().__init__(self.filelogger)
        self.configfile = configfile

    def discern_code(self, jpgpath):
        """
        识别验证码通过云打码
        :return:
        """
        boolresult, code = DiscernCode(self.logger).easy_decode_by_path(jpgpath)
        if boolresult:
            code = code.replace("b'", "").replace("'", "")
            return True, code
        else:
            return False, ""

    def set_logger(self, logger):
        self.logger = logger

    def login_once(self, threadrun, threadval):
        """
        本函数用于登陆
        :return:
        """
        self.localVal.sn = requests.session()
        # 该机制是为了防止验证码被过快调用造成不必要的浪费
        # 单线层
        # sql = "select `createtime` from `code` order by `createtime` DESC limit 1"
        # row = self.mysqlutils.SelectFromDBFetchOne_noyield(sql)
        # 多线程
        # 测试ip是否可以登录以及是否可以post请求
        # boolresult, msg = self.login("20", self.localVal.sn, self.localVal.proxys, threadval)
        # if msg == "IPInvalid":
        #     return boolresult, msg
        sql = "select `createtime` from `code` order by `createtime` DESC limit 1"
        queue = threadrun.thread_pool.thread_queue
        threadval.codesqlrows = None
        queue.put(("code", sql, threadval))
        while threadval.codesqlrows is None:
            time.sleep(3)
        row = threadval.codesqlrows
        if row:
            if MDateTime().get_diff(row[0]) < 20:
                self.logger.info("为防止过度调用验证码识别本次不予识别并登陆")
                return False, "await"
        result = self.set_ipaddr()
        if not result:
            """
            这里有几个逻辑
            1是否是IP代理的问题
            """
            BoolResult, errString, r = facade.BaseRequest(
                self.Baseurl + "/pubsearch/portal/uiIndex.shtml")
            if BoolResult:
                return False, "检查http://2019.ip138.com/ic.asp 问题"
            else:
                self.logger.info("IP错误,原因不明")
                return False, "IPInvalid"
        # 获取验证码图片
        isuse = self.get_verification_code()
        if isuse == "IP":
            self.logger.info("IP被封了")
            return False, "IPError"
        if not isuse:
            return False, "IPInvalid"

        # 解码
        boolresult, code = self.discern_code(isuse)
        if not boolresult:
            self.logger.info("本次识别失败")
            # return login_once(username, j_username, j_password, proxy, logger, t)
            return False, "识别验证码失败"
        # cookiename = self.ipaddr + "_" + str(getTodayDate()) + "_" + username + "_login.txt"
        cookiename = self.localVal.ipaddr + "_" + str(getTodayDate()) + "_" + self.localVal.userloginname
        create_time = datetime.datetime.now()
        sql = "insert into `code` (`cookiename`,`createtime`) values ('{}','{}')".format(cookiename,
                                                                                         create_time)
        threadval.result_queue.put(sql)
        # 登陆
        self.logger.info("开始登陆:" + self.localVal.ipaddr + ":" + self.localVal.userloginname)
        if threadval.source == "kuaidaili":
            stat = 2
        else:
            stat = 3
        threadval.setstat = stat
        return self.login(code, self.localVal.sn, self.localVal.proxys, threadval)


class ThreadRun(MThreadingRun):
    def __init__(self, num):
        self.localVal = threading.local()
        self.down = sipogovLogin()
        self.down.set_localVal(self.localVal)
        super(ThreadRun, self).__init__(num, self.down.filelogger)
        self.logger = self.down.filelogger

    def getTask(self, *args, **kwargs):
        # sysstr = platform.system()
        # if sysstr == "Windows":
        #     ip = GetLocalIPByPrefix("192.168.")
        # else:
        #     ip = get_local_ip("enp2s0")
        # if ip == "192.168.30.179":
        #     source = "mimvp"
        # else:
        #     source = "kuaidaili"

        # ip = "192.168.30.179"
        source = "mimvp"
        # print(ip)
        # source = "school"
        print(source)
        time.sleep(3)
        if self.thread_pool.work_queue_is_empty():
            rows = self.down.db.cookies.find({"stat": {"$in": [2, 3]},"origin": self.down.Baseurl}, {"ip": 1})
            for row in rows:
                sql = "update proxy_pool set stat=1 where ipaddr='{}' and stat != -1".format(row["ip"])
                self.down.mysqlutils.ExeSqlToDB(sql)
            sql = "select `proxy`,`update_time`,`source` from `proxy_pool` where `source`='{}' and stat in (2,-2) and `interval` >= 3600  ORDER BY `interval` DESC limit 10;".format(
                source)
            statrows = self.down.mysqlutils.SelectFromDB(sql)
            if source == "other" and len(statrows) < 5:
                sql = "update proxy_pool set stat=0 where `source`='school'"
                self.down.mysqlutils.ExeSqlToDB(sql)
            if statrows:
                return (1, statrows)
            else:
                self.logger.info("没有状态为0的代理")
                return (0, "")
        else:
            # self.logger.info("任务队列不为空")
            return (0, "")

    def setTask(self, results=None, *args, **kwargs):
        num, rows = results
        if num == 0:
            time.sleep(10)
            return True
        for row in rows:
            self.add_job(self.func, (num, row), *args, **kwargs)
        time.sleep(10)

    def fun(self, threadval, *args, **kwargs):
        self.logger.info("开始一次登录")
        rowtuple = args[0]
        num, rowmysql = rowtuple
        self.logger.info("num is {}".format(str(num)))
        rowscookieuser = self.down.db.cookies.find({"stat": {"$in": [2, 3]}, "source": rowmysql[2]})
        if rowmysql[2] == "mimvp":
            count = 15
        else:
            count = 15
        if rowscookieuser.count() >= count:
            self.logger.info("有{}个有效cookies　不登录".format(count))
            time.sleep(30)
            return
        if num == 1:
            self.down.set_proxys(rowmysql[0], rowmysql[2])
            self.down.set_ipaddr(self.down.localVal.proxys)
            # 代表新的代理需要设置cookie,检查cookies是否已经存在
            rows = self.down.db.cookies.find(
                {"ip": self.down.localVal.ipaddr, "source": rowmysql[2], "stat": {"$in": [0, 1, 2, 3]}})
            if rows.count() == 0:
                threadval.source = rowmysql[2]
                # 然后重新登陆
                threadval.timestat = 4
                boolresult = self.down.set_user(self.thread_pool.thread_queue, threadval)
                if not boolresult:
                    self.logger.info("判断到user 都在被使用 不登陆 状态写0等待下次登陆".format(rowmysql[0]))
                    sql = "update proxy_pool set stat=0 where `proxy` = '{}' and `source`='{}'".format(rowmysql[0],
                                                                                                       rowmysql[2])
                    threadval.result_queue.put(sql)
                    return True
                self.localVal.sn = requests.session()
                boolresult, strings = self.down.check_user(self.localVal.userloginname, self.localVal.sn,
                                                           self.localVal.proxys)
                if not boolresult:
                    if strings == "failure":
                        sql = "update `user` set stat=-1 where `username`='{}'".format(self.localVal.userloginname)
                        # self.mysqlutils.ExeSqlToDB(sql)
                        threadval.result_queue.put(sql)
                        return False, "用户有问题"
                self.logger.info("判断到ip 已无0,-1,1的状态 开始新的登录ip为{}".format(rowmysql[0]))
                # 开始检查代理有效性
                msg = self.down.check_proxys_is_used(self.down.localVal.proxys)
                if msg == "IP":
                    sql = "update proxy_pool set stat=-3 where `proxy` = '{}' and `source`='{}'".format(rowmysql[0],
                                                                                                        rowmysql[2])
                    threadval.result_queue.put(sql)
                    return True
                if not msg:
                    self.logger.info("代理失效")
                    sql = "update proxy_pool set stat=-1 where `proxy` = '{}' and `source`='{}'".format(rowmysql[0],
                                                                                                        rowmysql[2])
                    threadval.result_queue.put(sql)
                    return True
                # 开始检查cookie中状态为-1的cookie有没有有效的 可以直接使用

                rows = self.down.db.cookies.find(
                    {"ip": self.down.localVal.ipaddr, "source": rowmysql[1], "stat": -1,
                     "origin": self.down.Baseurl})
                for row in rows:
                    # 设置为通过验证的代理 防止cookie里代理端口不能用
                    proxy = rowmysql[0]  # row["proxy"]
                    bytess = row["cookie"]
                    cookie = pickle.loads(bytess)
                    self.down.set_proxys(proxy, rowmysql[1])
                    self.down.set_ipaddr(self.down.localVal.proxys)
                    self.down.localVal.cooke = cookie

                    rbool, msg = self.down.is_login_and_ip(requests.session(), self.down.localVal.proxys, cookie)

                    if rbool:
                        self.logger.info("检查到存在的cookie有效,-1状态将写0{}".format(row["proxy"]))
                        # 状态为没使用状态，其他状态维持
                        self.down.db.cookies.update({"_id": row["_id"]},
                                                    {"$set": {"stat": 0, "proxy": proxy,
                                                              "updatetime": MDateTime.get_now_datetime(
                                                                  struct_time=time.localtime(time.time()))}})
                        sql = "update proxy_pool set stat=1 where `ipaddr` = '{}' and `source`='{}' and `stat` != -1".format(
                            self.down.localVal.ipaddr,
                            rowmysql[1])
                        threadval.result_queue.put(sql)
                        return True
                    else:
                        self.logger.info("检查到存在的cookie无效,如果是登陆失败状态将写-2{}".format(row["proxy"]))
                        # 如果登陆失败
                        if msg == "登陆失败":
                            self.logger.info("检查到cookie状态登陆失败写-3{}".format(row["proxy"]))
                            # 代表代理没问题 只是cookies永久失效
                            self.down.db.cookies.update({"_id": row["_id"]},
                                                        {"$set": {"stat": -3,
                                                                  "updatetime": MDateTime.get_now_datetime(
                                                                      struct_time=time.localtime(time.time()))}})

                boolresult, msg = self.down.login_once(self, threadval)
                self.logger.info("登陆状态为{}返回消息为{}".format(boolresult, msg))
                if boolresult:
                    sql = "update proxy_pool set stat=1 where `ipaddr` = '{}' and `source`='{}' and stat != -1".format(
                        self.down.localVal.ipaddr, rowmysql[2])
                    threadval.result_queue.put(sql)
                else:
                    if msg == "awit":
                        time.sleep(3)
                        return True
                    if msg == "IPError":
                        # 代理被封
                        sql = "update proxy_pool set stat=-2 where `proxy` = '{}' and `source`='{}'".format(rowmysql[0],
                                                                                                            rowmysql[2])
                        threadval.result_queue.put(sql)
                    elif msg == "IPInvalid":
                        print("*********************")
                        sql = "update proxy_pool set stat=-1 where `proxy` = '{}' and `source`='{}'".format(rowmysql[0],
                                                                                                            rowmysql[2])
                        threadval.result_queue.put(sql)
                    else:
                        # 开始检查代理有效性
                        msg = self.down.check_proxys_is_used(self.down.localVal.proxys)
                        if msg == "IP":
                            sql = "update proxy_pool set stat=-2 where `proxy` = '{}' and `source`='{}'".format(
                                rowmysql[0],
                                rowmysql[2])
                            threadval.result_queue.put(sql)
                            return True
                        if not msg:
                            self.logger.info("代理失效")
                            sql = "update proxy_pool set stat=-1 where `proxy` = '{}' and `source`='{}'".format(
                                rowmysql[0], rowmysql[2])
                            threadval.result_queue.put(sql)
            else:
                sql = "update proxy_pool set stat=1 where `ipaddr` = '{}' and `source`='{}' and stat != -1".format(
                    self.down.localVal.ipaddr, rowmysql[2])
                threadval.result_queue.put(sql)
                self.logger.info(
                    "还有 0,1的状态 不进行登录登录ip为{}，剩余量为{}".format(self.down.localVal.ipaddr, str(rows.count())))
        else:
            self.logger.info("num 不是0 请检查")

    def thread_pool_hook(self, thread_pool_dicts, thread, args, kwargs) -> dict:
        return {}

    def dealresult(self, *args, **kwargs):
        time.sleep(3)
        for result in self.results:
            self.down.mysqlutils.ExeSqlToDB(result)

        self.deal_thread_queue()

    def deal_thread_queue(self):
        while not self.thread_pool.thread_queue.empty():
            result = self.thread_pool.thread_queue.get()
            if result[0] == "user":
                sql = result[1]
                row = self.down.mysqlutils.SelectFromDBFetchOne_noyield(sql)
                if not row:
                    sql = "update `user` set isajxcount=0 where `timestat`=%d"
                    # sql = sql % (int(time.strftime("%H")) // 4 + 1)
                    sql = sql % result[2].timestat
                    self.down.mysqlutils.ExeSqlToDB(sql)

                    result[2].sqlstat = 0
                    continue
                sql = "update `user` set `updatetime`='{}' where `username`='{}'".format(
                    MDateTime.get_msec_time()[0], row[0])
                self.down.mysqlutils.ExeSqlToDB(sql)
                # 2019-03-21 10:57:47
                if row[3].split(" ")[0] < MDateTime.get_msec_time()[0].split(" ")[0]:
                    sql = "update `user` set isajxcount=0 where `username`='{}'".format(row[0])
                    self.down.mysqlutils.ExeSqlToDB(sql)
                rowscookieuser = self.down.db.cookies.find({"stat": {"$in": [0, 1, 2, 3]}, "user": row[0]})
                if rowscookieuser.count() == 0:
                    result[2].sqlrows = row
                    result[2].sqlstat = 1
                else:
                    result[2].sqlstat = 0
            elif result[0] == "code":
                sql = result[1]
                row = self.down.mysqlutils.SelectFromDBFetchOne_noyield(sql)
                threadval = result[2]
                threadval.codesqlrows = row
            else:
                raise Exception("错误代码")
            self.thread_pool.thread_queue.task_done()

    # 上层重写的方法必须调用该方法
    def setProxy(self, proxysList=None):
        time.sleep(30)


if __name__ == "__main__":
    down = ThreadRun(1)
    down.thread_pool.set_work_queue(3)
    down.run()
